#include "Flag.h"

#include "../Game.h"
#include "../PhysicsLogic.h"
#include "../Renderer.h"

#include "../Stream.h"

#include <NxCooking.h>

ArrayList<Flag*> Flag::all;

const int Flag::cellCountX = 25;
const int Flag::cellCountY = 25;
const float Flag::cellSize = 0.2f;
const float Flag::thickness = Flag::cellSize * 0.3f;
const float Flag::bendingStiffness = 0.9f;
const float Flag::density = 0.4f;
const float Flag::poleHeight = 12.0f;

NxClothMesh* Flag::clothMesh = NULL;
NxClothMeshDesc Flag::cmDesc;

SurfaceRenderingData Flag::srdCloth;
SurfaceRenderingData Flag::srdPole;

NxReal *Flag::textCoords = NULL;

void Flag::Load() { //static
	cmDesc.numVertices = (cellCountX+1) * (cellCountY+1);
	cmDesc.numTriangles = cellCountX*cellCountY*2;
	cmDesc.pointStrideBytes = sizeof(NxVec3);
	cmDesc.triangleStrideBytes = 3*sizeof(NxU32);
	cmDesc.points = new NxVec3[cmDesc.numVertices];
	cmDesc.triangles = new NxU32[cmDesc.numTriangles*3];
	cmDesc.flags = 0;
	int i,j;
	NxVec3 *p = (NxVec3*)cmDesc.points;
	for (i = 0; i <= cellCountY; i++) {
		for (j = 0; j <= cellCountX; j++) {
			p->set(-cellSize*j, -cellSize*i, 0.0f);
			p++;
		}
	}
	NxU32 *id = (NxU32*)cmDesc.triangles;
	for (i = 0; i < cellCountY; i++) {
		for (j = 0; j < cellCountX; j++) {
			NxU32 i0 = i * (cellCountX+1) + j;
			NxU32 i1 = i0 + 1;
			NxU32 i2 = i0 + (cellCountX+1);
			NxU32 i3 = i2 + 1;
			if((j+i)%2) {
				*id++ = i0;
				*id++ = i2;
				*id++ = i1;
				*id++ = i1;
				*id++ = i2;
				*id++ = i3;
			} else {
				*id++ = i0;
				*id++ = i2;
				*id++ = i3;
				*id++ = i0;
				*id++ = i3;
				*id++ = i1;
			}
		}
	}

	//calculating texture coords
	textCoords = new NxReal[cmDesc.numVertices*2];

	for(NxU32 i = 0; i < cmDesc.numTriangles; ++i)	{
		for(int k = 0; k < 3; ++k) {
			const NxU32 id = ((NxU32*)cmDesc.triangles)[i*3 + k];

			const NxU32 cellX = id % (cellCountX+1);
			const NxU32 cellY = id / (cellCountX+1);
			const float tx = float(cellX)/float(cellCountX);
			const float ty = float(cellY)/float(cellCountY);

			textCoords[2*id] = tx;
			textCoords[2*id+1] = ty;
		}
	}

	//cooking:

	NxCookingInterface *cookingInterface = NxGetCookingLib(NX_PHYSICS_SDK_VERSION);

	cookingInterface->NxInitCooking();

	MemoryWriteBuffer wb;
	if(!cookingInterface->NxCookClothMesh(cmDesc, wb)) {
		Game::log.LogError("Failed to cook cloth mesh.");
	}
	MemoryReadBuffer rb(wb.data);
	clothMesh = PhysicsLogic::GetPhysicsSDK()->createClothMesh(rb);

	//rendering data:

	srdCloth.SetTexture(Textures::TEXTURE_FLAG);
	srdPole.SetTexture(Textures::TEXTURE_WOODEN_POLE);
}

Flag::Flag(const NxVec3& pos) {
	this->position = pos;

	//cloth:

	const NxU32 tearMemoryFactor = 1;
	NxU32 maxVertices = tearMemoryFactor * cmDesc.numVertices;
	NxU32 maxIndices = 3 * cmDesc.numTriangles;
	NxU32 *numVertices = new NxU32(cmDesc.numVertices);
	NxU32 *numIndices  = new NxU32(3 * cmDesc.numTriangles);

	NxVec3 *vertices = (NxVec3*)calloc(maxVertices, sizeof(NxVec3));
	NxVec3 *normals = (NxVec3*)calloc(maxVertices, sizeof(NxVec3));
	NxU32 *indices = (NxU32*)calloc(maxIndices, sizeof(NxU32));

	NxMeshData receiveBuffers;
	receiveBuffers.verticesPosBegin = &(vertices[0].x);
	receiveBuffers.verticesPosByteStride = sizeof(NxVec3);
	receiveBuffers.verticesNormalBegin = &(normals[0].x);
	receiveBuffers.verticesNormalByteStride = sizeof(NxVec3);
	receiveBuffers.maxVertices = maxVertices;
	receiveBuffers.numVerticesPtr = numVertices;

	receiveBuffers.indicesBegin = &(indices[0]);
	receiveBuffers.indicesByteStride = sizeof(NxU32);
	receiveBuffers.maxIndices = maxIndices;
	receiveBuffers.numIndicesPtr = numIndices;

	NxClothDesc cDesc;
	cDesc.flags |= NX_CLF_BENDING;
	cDesc.flags |= NX_CLF_VISUALIZATION;
	if(PhysicsLogic::IsHardwareAccelerationSupported()) {
		cDesc.flags |= NX_CLF_HARDWARE;
	}
	cDesc.density = 1.0f;
	cDesc.bendingStiffness = bendingStiffness;
	cDesc.thickness = thickness;
	cDesc.selfCollisionThickness = thickness;
	cDesc.clothMesh = clothMesh;
	cDesc.meshData = receiveBuffers;
	cDesc.globalPose.t = pos + NxVec3(0, poleHeight, 0);

	cloth = PhysicsLogic::GetScene()->createCloth(cDesc);
	int c=0;
	for(NxU32 i=0; i<cloth->getNumberOfParticles(); i += cellCountX+1) {
		cloth->attachVertexToGlobalPosition(i, NxVec3(0, poleHeight-c*cellSize, 0)+pos);
		++c;
	}

	//wind force field:

	NxForceFieldLinearKernelDesc linearKernelDesc;

	linearKernel = PhysicsLogic::GetScene()->createForceFieldLinearKernel(linearKernelDesc);

	NxForceFieldDesc fieldDesc;
	fieldDesc.kernel = linearKernel;
	fieldDesc.coordinates = NX_FFC_CARTESIAN;
	fieldDesc.pose.t = pos;
	fieldDesc.rigidBodyType = NX_FF_TYPE_NO_INTERACTION;

	forceField = PhysicsLogic::GetScene()->createForceField(fieldDesc);

	NxBoxForceFieldShapeDesc boxDesc;
	boxDesc.pose.t.y += poleHeight;
	boxDesc.dimensions = NxVec3(8,8,8);

	NxForceFieldShape *boxForceFieldShape = forceField->getIncludeShapeGroup().createShape(boxDesc);

	SetWind(PhysicsLogic::GetWind());
}

Flag::~Flag() {
	//cloth:

	NxMeshData& meshData = cloth->getMeshData();

	delete meshData.numVerticesPtr;
	delete meshData.numIndicesPtr;
	free(meshData.verticesPosBegin);
	free(meshData.verticesNormalBegin);
	free(meshData.indicesBegin);

	PhysicsLogic::GetScene()->releaseCloth(*cloth);

	//wind force field:

	PhysicsLogic::GetScene()->releaseForceField(*forceField);
	PhysicsLogic::GetScene()->releaseForceFieldLinearKernel(*linearKernel);
}

void Flag::SetWind(const NxVec3& force) {
	linearKernel->setConstant(force);
	linearKernel->setNoise(force);
}

void Flag::Draw() {
	glPushMatrix();
	glTranslatef(position.x, position.y+poleHeight*0.5f, position.z);
	Renderer::SetMaterial(srdPole);
	Renderer::DrawBox(NxVec3(0.1f, poleHeight*0.5f, 0.1f));
	glPopMatrix();

	const NxMeshData& mData = cloth->getMeshData();

	const NxU32 indicesCount = *mData.numIndicesPtr/3;

	const NxU32 *triangles = (NxU32*)mData.indicesBegin;
	const NxVec3 *vertices = (NxVec3*)mData.verticesPosBegin;
	const NxVec3 *normals = (NxVec3*)mData.verticesNormalBegin;

	//glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

	Renderer::SetMaterial(srdCloth);

	glDisable(GL_CULL_FACE);
	//normal render code, immediate mode
	/*for(NxU32 i = 0; i < indicesCount; ++i)	{
		glBegin(GL_TRIANGLES);
		for(int k = 0; k < 3; ++k) {
			const NxU32 id = (NxU32)triangles[i*3 + k];

			const NxVec3& tn = normals[id];
			glNormal3f(tn.x, tn.y, tn.z);

			const NxU32 cellX = id % (cellCountX+1);
			const NxU32 cellY = id / (cellCountX+1);
			const float tx = float(cellX)/float(cellCountX);
			const float ty = 1.0f-float(cellY)/float(cellCountY);
			glTexCoord2f(tx, ty);

			const NxVec3& v = vertices[id];
			glVertex3f(v.x, v.y, v.z);
		}
		glEnd();
	}*/

	//render code with vertex arrays
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);

	glVertexPointer(3, GL_FLOAT, 0, &vertices[0]);
	glNormalPointer(GL_FLOAT, 0, &normals[0]);
	glTexCoordPointer(2, GL_FLOAT, 0, &textCoords[0]);

	glDrawElements(GL_TRIANGLES, indicesCount*3 , GL_UNSIGNED_INT, &triangles[0]);

	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
	glDisableClientState(GL_VERTEX_ARRAY);

	glEnable(GL_CULL_FACE);

	//Normals
	/*
	SurfaceRenderingData srdR;
	SurfaceRenderingData srdG;
	srdR.SetColor(1,0,0,1);
	srdG.SetColor(0,1,0,1);
	glBegin(GL_LINES);
	for(NxU32 i=0; i<*mData.numVerticesPtr; ++i) {
		const NxVec3& p1 = vertices[i];
		const NxVec3 p2 = p1 + normals[i]/4;
		Renderer::SetMaterial(srdR);
		glVertex3f(p1.x, p1.y, p1.z);
		Renderer::SetMaterial(srdG);
		glVertex3f(p2.x, p2.y, p2.z);
	}
	glEnd();
	*/
}
